import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.swing.JLabel;

public class MonteCarloMultithread implements Runnable {

	private ArrayList<Variables> listVariables;
	private MonteCarloApp viewToUpdate;
	private Variables var;
	private JLabel label;
	private String setNb;
	
	public final String TYPE = "Multithread";
	
	private static final int POOL_SIZE = 10; //number of threads per pool
	
	/**
	 * Constructor
	 * @param viewToUpdate
	 */
	public MonteCarloMultithread (MonteCarloApp viewToUpdate) 
	{
		this.viewToUpdate = viewToUpdate;
		this.loadCSVfile();
	}
	
	/**
	 * Constructor
	 * @param a
	 * @param var
	 */
	public MonteCarloMultithread (MonteCarloApp a, Variables var) {
		this(a); // to call the first constructor
		this.var = var;
		this.loadCSVfile();
	}
	
	/**
	 * Constructor
	 * @param a
	 * @param var
	 * @param label
	 */
	public MonteCarloMultithread (MonteCarloApp a, Variables var, String setNb, JLabel label) {
		this(a);
		this.var = var;
		this.label = label;
		this.setNb = setNb;
		this.loadCSVfile();
	}
	
	public void loadCSVfile()
	{
		ArrayList<Variables> list = new ArrayList<Variables>();

		String line;
		StringTokenizer st;
		
		// attributes
		String callputflag = "a";
		double s = -1;
		double x = -1;
		double t = -1;
		double r = -1;
		double b = -1;
		double v = -1;
		int nSteps = -1;
		int nSimulations = -1;

		try{
			BufferedReader buff = new BufferedReader(new FileReader("file.csv"));
			try {

				// Lire le fichier ligne par ligne
				// La boucle se termine quand la méthode affiche "null"
				while ((line = buff.readLine()) != null) 
				{
					//System.out.println(line);

					st = new StringTokenizer(line, ";");
					while (st.hasMoreTokens()) 
					{ 
						callputflag = st.nextToken();
						s = Double.parseDouble(st.nextToken());
						x = Double.parseDouble(st.nextToken());
						t = Double.parseDouble(st.nextToken());
						r = Double.parseDouble(st.nextToken());
						b = Double.parseDouble(st.nextToken());
						v = Double.parseDouble(st.nextToken());
						nSteps = Integer.parseInt(st.nextToken());
						nSimulations = Integer.parseInt(st.nextToken());
					} 

					Variables var = new Variables(callputflag,s, x, t, r, b, v, nSteps, nSimulations);
					list.add(var);
				}

				buff.close(); //Lecture fini donc on ferme le flux
			}

			catch (IOException e)
			{
				System.out.println(e.getMessage());
				System.exit(1);
			}
		}
		catch (IOException e) 
		{
			System.out.println(e.getMessage());
			System.exit(1);
		}
		this.listVariables = list;
	}
	
	
	/**
	 * To do the Monte Carlo calculation with multithreads
	 * 
	 * @param callPutFlag
	 * @param s
	 * @param x
	 * @param t
	 * @param r
	 * @param b
	 * @param v
	 * @param nSteps
	 * @param nSimulations
	 * @return
	 */
	public double computeMonteCarlo(Variables var) {
		
		String callPutFlag = var.getCallputFlag();
		double s = var.getS();
		double x = var.getX();
		double t = var.getT();
		double r = var.getR();
		double b = var.getB();
		double v = var.getV();
		int nSteps = var.getnSteps();
		int nSimulations = var.getnSimulations();
		
		List<Future<Double>> list = new ArrayList<Future<Double>>();

		long time_start = System.currentTimeMillis();
		
		ExecutorService executor = Executors.newFixedThreadPool(POOL_SIZE);

		for(int i=0; i<nSimulations; i++)
		{
			Callable<Double> computation = new MCsimulationTask(callPutFlag, s, x, t, r, b, v, nSteps);

			Future<Double> submit = executor.submit(computation);
			list.add(submit);
		}

		Double sum = (double) 0;
		int count = 0;
		
		for(Future<Double> future : list) {

			try {
				sum = sum + future.get();
				count = count + 1;
				viewToUpdate.displayIntermediateValue(TYPE, sum/count, this.setNb, this.label);
			}

			catch (InterruptedException e) {
				e.printStackTrace();
			} 

			catch (ExecutionException e) {
				e.printStackTrace();
			}
		}

		double estim = (double) 0;
		estim = Math.exp(-r * t)*(sum/nSimulations); // to compute the average 

		// To calculate the time of execution
		long time_end = System.currentTimeMillis();
		long time_exec = time_end - time_start;	
		
		viewToUpdate.displayFinalValue(TYPE, estim, time_exec, this.setNb, this.label);
		
		executor.shutdown();	
		return estim;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		computeMonteCarlo(var);
	}

	/**
	 * Getter list variables
	 */
	public ArrayList<Variables> getListVariables() {
		return this.listVariables;
	}

	/**
	 * Setter list variables
	 */
	public void setListVariables(ArrayList<Variables> newList) {
		this.listVariables = newList;
	}
	
	public JLabel getLabel() {
		return this.label;
	}
}